﻿#region using statements

using System;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

using NGen;
using NGen.Managers.GUI;
using NGen.Managers.GameState;
using NGen.Managers.Input;
using NGen.Managers.Pathfinding;

using Nuclex.Input;

using XNAGameDemo.GUI;

#endregion

namespace XNAGameDemo.GameStates
{
	public class GameStatePlay2 : GameState, IMouseListener, IKeyboardListener
	{
		/// <summary>
		/// 	random number generator
		/// </summary>
		private static readonly Random sc_random = new Random(DateTime.Now.Millisecond);

		/// <summary>
		/// 	The gui to load
		/// </summary>
		private readonly GUIElementPlay2State m_gui = new GUIElementPlay2State();

		/// <summary>
		/// 	did the last middle click remove or add a wall?
		/// 	used whenever middle click is held
		/// </summary>
		private bool m_addWall;

		/// <summary>
		/// 	Ending location for pathfinding
		/// </summary>
		private Point m_endLoc;

		//contants used to resize the tiles based on screen resolution and
		//width/height of grid
		private double m_heightConst;

		/// <summary>
		/// 	stores the path returned from the pathfinding manager
		/// </summary>
		private Point[] m_path;

		/// <summary>
		/// 	width of the grid
		/// </summary>
		private int m_sizeX;

		/// <summary>
		/// 	height of the grid
		/// </summary>
		private int m_sizeY;

		/// <summary>
		/// 	Starting location for pathfinding
		/// </summary>
		private Point m_startLoc;

		/// <summary>
		/// 	2d array contains wall locations
		/// </summary>
		private bool[,] m_wallMap;

		/// <summary>
		/// 	image to use for the wall
		/// </summary>
		private Texture2D m_wallTexture;

		private double m_widthConst;

		#region IKeyboardListener Members

		public void KeyPressed(Keys p_key) {
			//keyboard shortcuts for the buttons
			switch(p_key) {
				case Keys.Space:
					_FindPath();
					break;
				case Keys.Enter:
					_RandomizeWorld();
					break;
				case Keys.Delete:
					_ClearPath();
					break;
			}
		}

		public void KeyReleased(Keys p_key) {}

		#endregion

		#region IMouseListener Members

		public void MousePressed(MouseButtons p_button) {
			//if the mouse if over the gui, dont interact with the game world
			if(GameEngine.GetService<IManagerGUI>().MouseOverGUI()) {
				return;
			}

			//gets the mouse state for x and y positions
			var mouse = GameEngine.GetService<IManagerInput>().GetMouseState();
			switch(p_button) {
					//on left click, place starting location
				case MouseButtons.Left:
					m_startLoc.X = (int) (mouse.X / m_widthConst);
					m_startLoc.Y = (int) (mouse.Y / m_heightConst);
					m_wallMap[m_startLoc.X, m_startLoc.Y] = false;
					break;

					//on right click place ending location
				case MouseButtons.Right:
					m_endLoc.X = (int) (mouse.X / m_widthConst);
					m_endLoc.Y = (int) (mouse.Y / m_heightConst);
					m_wallMap[m_endLoc.X, m_endLoc.Y] = false;
					break;

					//on middle click toggle a wall
				case MouseButtons.Middle:
					m_wallMap[(int) (mouse.X / m_widthConst), (int) (mouse.Y / m_heightConst)] ^= true; //XOR operation
					m_addWall = m_wallMap[(int) (mouse.X / m_widthConst), (int) (mouse.Y / m_heightConst)];

					break;
			}
		}

		public void MouseReleased(MouseButtons p_button) {}

		public void MouseMoved(float p_x, float p_y) {
			//if mouse is moved while middle click is held, continue to either add 
			//or remove wall cells
			var mouse = GameEngine.GetService<IManagerInput>().GetMouseState();
			if(mouse.MiddleButton ==
			   ButtonState.Pressed) {
				m_wallMap[(int) (p_x / m_widthConst), (int) (p_y / m_heightConst)] = m_addWall;
			}
		}

		public void MouseWheelScroll(float p_ticks) {}

		#endregion

		protected override void _Initialize() {
			//add the gui, turn on input listening
			GameEngine.GetService<IManagerGUI>().AddElement(m_gui);
			GameEngine.GetService<IManagerInput>().AddKeyboardListener(this);
			GameEngine.GetService<IManagerInput>().AddMouseListener(this);
			GameEngine.GetService<IManagerPathfinding>().MaintainClosedList = true;

			//load the wall texture
			m_wallTexture = GameEngine.GameContent.Load<Texture2D>("Content\\Textures\\tile");

			//set the grid widht/height
			m_sizeX = 50;
			m_sizeY = 50;

			//world size has changed, update its values
			_WorldSizeChanged();

			//hook up the button clicks to the functions which need to be called
			m_gui.OnGoButtonClick += _FindPath;
			m_gui.OnRandomizeButtonClick += _RandomizeWorld;
			m_gui.OnClearButtonClick += _ClearPath;
			m_gui.OnToggleDebugDisplayButtonClicked += delegate {
			                                           	GameEngine.GetService<IManagerPathfinding>().MaintainClosedList = !GameEngine.GetService<IManagerPathfinding>().MaintainClosedList;
			                                           };
		}

		protected override void _Cleanup() {
			//remove the gui, turn off input listening
			GameEngine.GetService<IManagerGUI>().RemoveElement(m_gui);
			GameEngine.GetService<IManagerInput>().RemoveMouseListener(this);
			GameEngine.GetService<IManagerInput>().RemoveKeyboardListener(this);
		}

		protected override void _OnFocusLost() {}

		protected override void _OnFocusGained() {}

		protected override void _Update(GameTime p_gameTime) {}

		protected override void _Draw(GameTime p_gameTime) {
			GameEngine.GraphicsDeviceManager.GraphicsDevice.Clear(Color.Transparent);

			GameEngine.SpriteBatch.Begin();

			//draw the path tiles
			if(m_path != null) {
				//draws all tiles searched, if it is being tracked
				if(GameEngine.GetService<IManagerPathfinding>().MaintainClosedList) {
					foreach(var cell in GameEngine.GetService<IManagerPathfinding>().ClosedList) {
						GameEngine.SpriteBatch.Draw(m_wallTexture, new Vector2((float) (cell.GetLocation.X * m_widthConst), (float) ((cell.GetLocation.Y * m_heightConst))), null, Color.DarkRed, 0, Vector2.Zero, new Vector2((float) (m_widthConst / m_wallTexture.Width), (float) (m_heightConst / m_wallTexture.Height)), SpriteEffects.None, 0);
					}

					//draws all tiles seen, but not searched
					foreach(var cell in GameEngine.GetService<IManagerPathfinding>().OpenList) {
						GameEngine.SpriteBatch.Draw(m_wallTexture, new Vector2((float) (cell.GetLocation.X * m_widthConst), (float) ((cell.GetLocation.Y * m_heightConst))), null, Color.Blue, 0, Vector2.Zero, new Vector2((float) (m_widthConst / m_wallTexture.Width), (float) (m_heightConst / m_wallTexture.Height)), SpriteEffects.None, 0);
					}
				}

				//draw the path
				for(var x = 0; x < m_path.Length - 1; x++) {
					GameEngine.SpriteBatch.Draw(m_wallTexture, new Vector2((float) (m_path[x].X * m_widthConst), (float) ((m_path[x].Y * m_heightConst))), null, new Color(255, 255, 0, 255), 0, Vector2.Zero, new Vector2((float) (m_widthConst / m_wallTexture.Width), (float) (m_heightConst / m_wallTexture.Height)), SpriteEffects.None, 0);
				}
			}

			//draw the walls
			for(var i = 0; i < m_sizeX; ++i) {
				for(var j = 0; j < m_sizeY; ++j) {
					if(!m_wallMap[i, j]) {
						continue;
					}
					GameEngine.SpriteBatch.Draw(m_wallTexture, new Vector2((int) (i * m_widthConst), (int) (j * m_heightConst)), null, Color.White, 0, Vector2.Zero, new Vector2((float) (m_widthConst / m_wallTexture.Width), (float) (m_heightConst / m_wallTexture.Height)), SpriteEffects.None, 0);
				}
			}

			//draw the start location
			GameEngine.SpriteBatch.Draw(m_wallTexture, new Vector2((int) (m_startLoc.X * m_widthConst), (int) (m_startLoc.Y * m_heightConst)), null, Color.Green, 0, Vector2.Zero, new Vector2((float) (m_widthConst / m_wallTexture.Width), (float) (m_heightConst / m_wallTexture.Height)), SpriteEffects.None, 0);

			//draw the end location
			GameEngine.SpriteBatch.Draw(m_wallTexture, new Vector2((int) (m_endLoc.X * m_widthConst), (int) (m_endLoc.Y * m_heightConst)), null, Color.Red, 0, Vector2.Zero, new Vector2((float) (m_widthConst / m_wallTexture.Width), (float) (m_heightConst / m_wallTexture.Height)), SpriteEffects.None, 0);

			GameEngine.SpriteBatch.End();
		}

		private void _FindPath() {
			//get the optimal path from start to end location
			m_path = GameEngine.GetService<IManagerPathfinding>().FindPath(m_startLoc, m_endLoc);
		}

		private void _RandomizeWorld() {
			//randomize wall placements
			for(var i = 0; i < m_sizeX; ++i) {
				for(var j = 0; j < m_sizeY; ++j) {
					m_wallMap[i, j] = sc_random.NextDouble() > 0.7;
				}
			}

			//make sure start and end locations are not walls
			m_wallMap[m_startLoc.X, m_startLoc.Y] = false;
			m_wallMap[m_endLoc.X, m_endLoc.Y] = false;

			_ClearPath();
			GameEngine.GetService<IManagerPathfinding>().GridLayout = m_wallMap;
		}

		private void _ClearPath() {
			m_path = null;
		}

		private void _WorldSizeChanged() {
			//recalculate constants
			m_widthConst = GameEngine.GameWindow.ClientBounds.Width / (double) m_sizeX;
			m_heightConst = GameEngine.GameWindow.ClientBounds.Height / (double) m_sizeY;

			//reset starting and ending locations
			m_startLoc = new Point(1, 2);
			m_endLoc = new Point(m_sizeX - 2, m_sizeY - 2);
			m_addWall = false;

			m_wallMap = new bool[m_sizeX,m_sizeY];

			_RandomizeWorld();

			//initialize the pathfinding manager with new world dimensions
			GameEngine.GetService<IManagerPathfinding>().SetParams(m_sizeX, m_sizeY, 1, 0, m_wallMap);
		}
	}
}